package org.sonar.template.java.checks;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.check.Rule;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.*;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author chenze
 * @create: 2020-07-18 09:50
 */
@Rule(key = "UnusedPrivateFieldCheck")
public class UnusedPrivateFieldCheckRule extends IssuableSubscriptionVisitor {

    public static final Logger logger = LoggerFactory.getLogger(UnusedPrivateFieldCheckRule.class);

    private static final Tree.Kind[] ASSIGNMENT_KINDS = {
            Tree.Kind.ASSIGNMENT,
            Tree.Kind.MULTIPLY_ASSIGNMENT,
            Tree.Kind.DIVIDE_ASSIGNMENT,
            Tree.Kind.REMAINDER_ASSIGNMENT,
            Tree.Kind.PLUS_ASSIGNMENT,
            Tree.Kind.MINUS_ASSIGNMENT,
            Tree.Kind.LEFT_SHIFT_ASSIGNMENT,
            Tree.Kind.RIGHT_SHIFT_ASSIGNMENT,
            Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT,
            Tree.Kind.AND_ASSIGNMENT,
            Tree.Kind.XOR_ASSIGNMENT,
            Tree.Kind.OR_ASSIGNMENT};

    private List<ClassTree> classes = new ArrayList<>();
    private ListMultimap<Symbol, IdentifierTree> assignments = ArrayListMultimap.create();
    private Set<String> unknownIdentifiers = new HashSet<>();
    private boolean hasNativeMethod = false;

    /**
     * 类上面是否有注解
     * @lombok.Data、@lombok.Getter
     */
    private List<String> ignoreVisitAnnotationArr = Stream.of("Data","Getter").collect(Collectors.toList());

    private boolean ignoreVisit = false;


    @Override
    public List<Tree.Kind> nodesToVisit() {
        logger.info("this method==============={}","nodesToVisit");
        return Arrays.asList(Tree.Kind.CLASS, Tree.Kind.METHOD, Tree.Kind.EXPRESSION_STATEMENT,Tree.Kind.VARIABLE, Tree.Kind.IDENTIFIER);
    }

    /**
     * 为什么没有调用这个方法
     * @param context
     */
    @Override
    public void leaveFile(JavaFileScannerContext context) {
        logger.info("this method==============={}","leaveFile");
        /*if (!hasNativeMethod) {
            classes.forEach(this::checkClassFields);
        }*/
        classes.clear();
        unknownIdentifiers.clear();
        assignments.clear();
        ignoreVisit = false;
        hasNativeMethod = false;
    }

    @Override
    public void visitNode(Tree tree) {
        if (!hasSemantic()) {
            return;
        }
        switch (tree.kind()) {
            case METHOD:
                checkIfNativeMethod((MethodTree) tree);
                break;
            case CLASS:
                this.classes.add((ClassTree) tree);
                this.ignoreVisit = this.checkAnnotation((ClassTree)tree);
                break;
            case EXPRESSION_STATEMENT:
                collectAssignment(((ExpressionStatementTree) tree).expression());
                break;
            case IDENTIFIER:
                collectUnknownIdentifier((IdentifierTree) tree);
                break;
            case VARIABLE:
                checkIfUnused((VariableTree)tree);
                break;
            default:
                throw new IllegalStateException("Unexpected subscribed tree.");
        }
    }


    /**
     * 校验class是否存在需要忽略类校验的注解
     * @param tree
     */
    private boolean checkAnnotation(Tree tree){

        ModifiersTree modifiers;
        if (tree.is(Tree.Kind.VARIABLE)) {
            modifiers = ((VariableTree)tree).modifiers();
        }else if(tree.is(Tree.Kind.CLASS)){
            modifiers = ((ClassTree)tree).modifiers();
        }else{
            return false;
        }

        List<AnnotationTree> annotations = modifiers.annotations();

        /**
         * 是否存在交集
         */
        List<String> section = annotations.stream()
                .map(annotationTree -> annotationTree.symbolType().name())
                .filter(annotationName -> ignoreVisitAnnotationArr.contains(annotationName))
                .collect(Collectors.toList());
        return !section.isEmpty();
    }


    private void collectUnknownIdentifier(IdentifierTree tree) {
        logger.info("this method==============={},treeName={}","collectUnknownIdentifier",tree.symbolType().name());
        if (tree.symbol().isUnknown() && !isMethodIdentifier(tree)) {
            unknownIdentifiers.add(tree.name());
        }
    }

    private static boolean isMethodIdentifier(IdentifierTree identifier) {
        logger.info("this method==============={},treeName={}","isMethodIdentifier",identifier.symbolType().name());
        Tree parent = identifier.parent();
        while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION, Tree.Kind.METHOD_REFERENCE)) {
            parent = parent.parent();
        }
        if (parent == null) {
            return false;
        }
        if (parent.is(Tree.Kind.METHOD_INVOCATION)) {
            return identifier.equals(ExpressionUtils.methodName((MethodInvocationTree) parent));
        } else {
            return identifier.equals(((MethodReferenceTree) parent).method());
        }
    }

    private void checkIfNativeMethod(MethodTree method) {
        if (ModifiersUtils.hasModifier(method.modifiers(), Modifier.NATIVE)) {
            hasNativeMethod = true;
        }
    }

/*    private void checkClassFields(ClassTree classTree) {
        logger.info("this method==============={},treeName={}","checkClassFields",classTree.symbol().name());
        classTree.members().stream()
                .filter(member -> member.is(Tree.Kind.VARIABLE))
                .map(VariableTree.class::cast)
                .forEach(this::checkIfUnused);
    }*/

    public void checkIfUnused(VariableTree tree) {


        if (ignoreVisit) {
            return;
        }

        logger.info("this method==============={},treeName={}","checkIfUnused",tree.symbol().name());
        Symbol symbol = tree.symbol();
        String name = symbol.name();

        if (hasNoAnnotation(tree)) {
            if (!symbol.isPrivate()
                    || !onlyUsedInVariableAssignment(symbol)
                    ||"serialVersionUID".equals(name)
                    || unknownIdentifiers.contains(name)) {
                return;
            }
        }else{
            if (this.checkAnnotation(tree)) {
                return;
            }

        }

        reportIssue(tree.simpleName(), "Remove this unused \"" + name + "\" private field.");

    }

    private boolean onlyUsedInVariableAssignment(Symbol symbol) {
        return symbol.usages().size() == assignments.get(symbol).size();
    }

    private static boolean hasNoAnnotation(VariableTree tree) {
        return tree.modifiers().annotations().isEmpty();
    }

    private void collectAssignment(ExpressionTree expressionTree) {
        if (expressionTree.is(ASSIGNMENT_KINDS)) {
            addAssignment(((AssignmentExpressionTree) expressionTree).variable());
        }
    }

    private void addAssignment(ExpressionTree tree) {
        ExpressionTree variable = ExpressionUtils.skipParentheses(tree);
        if (variable.is(Tree.Kind.IDENTIFIER)) {
            addAssignment((IdentifierTree) variable);
        } else if (variable.is(Tree.Kind.MEMBER_SELECT)) {
            addAssignment(((MemberSelectExpressionTree) variable).identifier());
        }
    }

    private void addAssignment(IdentifierTree identifier) {
        Symbol reference = identifier.symbol();
        if (!reference.isUnknown()) {
            assignments.put(reference, identifier);
        }
    }
}
